<?php
/**
 * Manages categories with localized labels.
 * 
 * @author Martin "DevelX" Jurča
 */
class Categories extends Module {
    protected $name = 'Categories';
    protected $administrable = true;
    protected $version = '2009-09-16';
    protected $details = array('dependencies' => array(),
            'observes' => array('Categories'));

    const CATEGORIES_TABLE = 'categories';
    const LOCALIZED_LABELS = 'categoriesLabels';
    private $categoriesTable = array('id' => 'iPN+', 'name' => 'sN',
            'parent' => 'iN');
    private $labelsTable = array('id' => 'iIN', 'lng' => "v'255'NI", 'label' =>
            'sN', 'description' => 'T');
    private $categoryRecord = array('table' => self::CATEGORIES_TABLE,
            'data' => array());
    private $labelRecord = array('table' => self::LOCALIZED_LABELS, 'data' =>
            array());

    public function  __construct(DBAPI $dbapi, RecordList $recordList) {
        parent::__construct($dbapi, $recordList);
        $this->categoryRecord['properties'] = $this->categoriesTable;
        $this->labelRecord['properties'] = $this->labelsTable;
    }

    public function install() {
        $this->dbdriver->createTable(self::CATEGORIES_TABLE,
                $this->categoriesTable);
        $this->dbdriver->createTable(self::LOCALIZED_LABELS,
                $this->labelsTable);
        Modules::getModule('Users')->registerRights('Categories',
                $this->getRights());
        return true;
    }

    public function uninstall() {
        $this->dbdriver->dropTable(self::CATEGORIES_TABLE);
        $this->dbdriver->dropTable(self::LOCALIZED_LABELS);
        return true;
    }

    public function initDisplay($param = null) {
        parent::initDisplay($param);
        $this->registerJS();
    }

    public function initDisplayAdminIndex() {
        $this->addMsg('clickCategoryToOpen', self::MSG_NOTICE);
    }

    public function display($param = null) {
        if (Kernel::getMode() == Kernel::ADMIN) {
            $this->displayAdmin($param);
        }
    }

    public function &onEditCategory(&$details, $source) {
        $result = null;
        Kernel::setRedirect($this->getLink());
        if (!Modules::getModule('Users')->checkRights('Categories',
                'edit categories')) {
            Modules::getModule('View')->addMessage($this->lang('
                    insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $category = $this->categoryRecord;
        $category['data'] = array('id' => $details['id'], 'name' =>
                $details['name'], 'parent' => $details['parent']);
        $this->dbapi->update($category);
        $this->dbdriver->delete($this->labelRecord['table'], '`id` = ' .
                $details['id']);
        if (isset($details['label']) && is_array($details['label'])) {
            foreach ($details['label'] as $id => $label) {
                if (($label != '') && ($details['lang'][$id] !== '')) {
                    $labelRec = $this->labelRecord;
                    $labelRec['data'] = array('id' => $category['data']['id'],
                            'lng' => $details['lang'][$id], 'label' => $label,
                            'description' => $details['description'][$id]);
                    $this->dbapi->save($labelRec);
                }
            }
        }
        return $result;
    }

    public function &onAddCategory(&$details, $source) {
        $result = null;
        Kernel::setRedirect($this->getLink());
        if (!Modules::getModule('Users')->checkRights('Categories',
                'create categories')) {
            Modules::getModule('View')->addMessage($this->lang(
                    'insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $category = $this->categoryRecord;
        $category['data'] = array('name' => $details['name'], 'parent' =>
                $details['parent']);
        $this->dbapi->save($category);
        if (isset($details['label']) && is_array($details['label'])) {
            foreach ($details['label'] as $id => $label) {
                if (($label != '') && ($details['lang'][$id] !== '')) {
                    $labelRec = $this->labelRecord;
                    $labelRec['data'] = array('id' => $category['data']['id'],
                            'lng' => $details['lang'][$id], 'label' => $label,
                            'description' => $details['description'][$id]);
                    $this->dbapi->save($labelRec);
                }
            }
        }
        return $result;
    }

    public function &onDelete(&$details, $source) {
        $result = null;
        Kernel::setRedirect($this->getLink());
        if (!Modules::getModule('Users')->checkRights('Categories',
                'delete categories')) {
            Modules::getModule('View')->addMessage($this->lang(
                    'insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $id = (int) substr($details, 0, -1);
        $this->dbdriver->delete($this->labelRecord['table'], '`id` = ' . $id);
        $this->dbdriver->delete($this->categoryRecord['table'], '`id` = ' .
                $id);
        return $result;
    }

    /**
     * Returns category record with localized name and description (if
     * available). The record is returned by pointer.
     *
     * @param int $id ID number of category.
     * @return Array
     */
    public function &getCategory($id) {
        $category = $this->categoryRecord;
        $category['data']['id'] = (int) $id;
        $this->dbapi->refresh($category);
        $this->loadLocalizedLabel($category);
        return $category;
    }

    /**
     * Returns ancestors of specified category as a list of category records
     * with loaded localized names and descriptions (if available). The list is
     * return by pointer.
     *
     * @param int $root ID number of root category. If ommited, root category
     *        is used.
     * @return Array List of categories.
     */
    public function &getCategoryList($root = 0, $orderBy = array()) {
        $categories = &$this->dbapi->load($this->categoryRecord,
                '`parent` = ' . (int) $root, $orderBy);
        $result = $categories;
        foreach ($result as &$category) {
            $this->loadLocalizedLabel($category);
        }
        foreach ($categories as &$category) {
            $children = &$this->getCategoryList($category['data']['id']);
            $result = array_merge($result, $children);
        }
        return $result;
    }

    /**
     * Returns child categories of specified category as a list of category
     * records with loaded localized names and descriptions (if available). The
     * list is returned by pointer.
     *
     * @param int $root ID number of root category. If ommited, root category
     *        is used.
     * @param Array $orderBy Array of properties to be used by dbapi to order
     *        the categories
     * @return Array List of categories
     */
    public function &getCategoryContent($root = 0, $orderBy = array()) {
        $categories = &$this->dbapi->load($this->categoryRecord,
                '`parent` = ' . (int) $root, $orderBy);
        foreach ($categories as &$category) {
            $this->loadLocalizedLabel($category);
        }
        return $categories;
    }

    /**
     * Returns ancestors of specified category as a tree of category records
     * with loaded localized names and descriptions (if available). The list is
     * returned by pointer.
     *
     * @param int $root ID number of root category. If ommited, root category
     *        is used.
     * @param Array Array of properties to be used by dbdapi to order the
     *        categories
     * @return Array Tree of categories.
     */
    public function &getCategoryTree($root = 0, $orderBy = array()) {
        $categories = &$this->dbapi->load($this->categoryRecord,
                '`parent` = ' . (int) $root, $orderBy);
        foreach ($categories as &$category) {
            $this->loadLocalizedLabel($category);
            $children = &$this->getCategoryTree($category['data']['id'],
                    $orderBy);
            $category['data']['children'] = $children;
        }
        return $categories;
    }

    /**
     * Displays administration interface for browsing the category tree.
     *
     */
    private function displayAdminBrowse($catId = 0) {
        if (!Modules::getModule('Users')->checkRights('Categories',
                'list categories')) {
            return;
        }
        if ($catId == 0) {
            $parent = false;
            $parentTitle = '';
            $category = '/';
        } else {
            $category = $this->categoryRecord;
            $category['data']['id'] = $catId;
            $this->dbapi->refresh($category);
            $parentTitle = $this->lang('parentTitle');
            $parent = $this->getLink("browse/{$category['data']['parent']}/");
            $category = $category['data']['name'];
        }
        $categories = &$this->dbapi->load($this->categoryRecord,
                '`parent` = ' . $catId);
        foreach ($categories as &$categ) {
            $this->loadLocalizedLabel($categ);
            $categ = $categ['data'];
            $categ['edit'] = $this->getLink("edit-category/{$categ['id']}/");
            $categ['delete'] = $this->getLink("{$categ['id']}/", 'delete');
            $categ['view'] = $this->getLink("browse/{$categ['id']}/");
        }
        $params = array('parent' => $parent, 'parentTitle' => $parentTitle,
                'category' => $category, 'categories' => $categories, 'edit' =>
                $this->lang('edit'), 'delete' => $this->lang('delete'));
        $this->template('browse', $params);
    }

    /**
     * Loads localized label and description of category into category record
     * as properties localname and description.
     *
     * @param Array $category Category record
     */
    private function loadLocalizedLabel(&$category) {
        $labels = &$this->dbapi->load($this->labelRecord, '`id` = ' .
                $category['data']['id'] . ' AND `lng` = \'' .
                $this->dbdriver->escapeString(Kernel::getLanguage()) . '\'');
        if (count($labels) > 0) {
            $category['data']['localname'] = $labels[0]['data']['label'];
            $category['data']['description'] =
                    $labels[0]['data']['description'];
            return;
        }
        if (strlen(Kernel::getLanguage()) > 2) {
            $labels = &$this->dbapi->load($this->labelRecord, '`id` = ' .
                   $category['data']['id'] . ' AND `lng` = \'' .
                   $this->dbdriver->escapeString(substr(Kernel::getLanguage(),
                   0, 2)) . '\'');
            if (count($labels) > 0) {
                $category['data']['localname'] = $labels[0]['data']['label'];
                $category['data']['description'] =
                        $labels[0]['data']['description'];
                return;
            }
        }
        $labels = &$this->dbapi->load($this->labelRecord, '`id` = ' .
                $category['data']['id'] . ' AND `lng` = \'' .
                $this->dbdriver->escapeString(Config::DEFAULT_LANG) . '\'');
        if (count($labels) > 0) {
            $category['data']['localname'] = $labels[0]['data']['label'];
            $category['data']['description'] =
                    $labels[0]['data']['description'];
            return;
        }
        $labels = &$this->dbapi->load($this->labelRecord, '`id` = ' .
                $category['data']['id']);
        if (count($labels) > 0) {
            $category['data']['localname'] = $labels[0]['data']['label'];
            $category['data']['description'] =
                    $labels[0]['data']['description'];
            return;
        }
        $category['data']['localname'] = $category['data']['name'];
        $category['data']['description'] = '';
    }

    /**
     * Displays administration interface for edditing categories.
     *
     * @param int $id
     */
    private function displayAdminEditCategory($id) {
        if (!Modules::getModule('Users')->checkRights('Categories',
                'edit categories')) {
            return;
        }
        $category = $this->categoryRecord;
        $category['data']['id'] = $id;
        $this->dbapi->refresh($category);
        $category = $category['data'];
        $labels = &$this->dbapi->load($this->labelRecord, '`id` = ' .
                $category['id']);
        foreach ($labels as &$label) {
            $label = $label['data'];
        }
        $categories = &$this->loadCategorySystemTree(0, 0, $category['id']);
        $params = array('category' => $category, 'labels' => $labels,
                'editCategory' => $this->lang('editCategory'), 'name' =>
                $this->lang('categoryName'), 'parent' =>
                $this->lang('categoryParent'), 'langCode' =>
                $this->lang('langCode'), 'label' => $this->lang('label'),
                'description' => $this->lang('description'), 'addLabel' =>
                $this->lang('addLabel'), 'save' => $this->lang('save'),
                'formStart' => $this->getForm('editCategory', true,
                'Categories'), 'localized' => $this->lang('localized'),
                'categories' => $categories);
        $this->template('editcategory', $params);
    }

    /**
     * Displays administration interface
     *
     * @param mixed $param Display parameter.
     */
    private function displayAdmin($param) {
        if (!Modules::getModule('Users')->checkRights('Categories',
                'view admin')) {
            return;
        }
        if ($param == 'submenu') {
            $this->displayAdminSubmenu();
        } else {
            $details = Modules::getModule('View')->getUrl();
            if (count($details) == 2) {
                if ($details[1] == 'add-category') {
                    $this->displayAdminAddCategory();
                }
            } elseif (count($details) == 1) {
                $this->displayAdminBrowse();
            } elseif (count($details) == 3) {
                if ($details[1] == 'edit-category') {
                    $this->displayAdminEditCategory($details[2]);
                } elseif ($details[1] == 'browse') {
                    $this->displayAdminBrowse($details[2]);
                }
            }
        }
    }

    /**
     * Registers required JS files.
     *
     */
    private function registerJS() {
        if (Kernel::getMode() == Kernel::ADMIN) {
            $details = Modules::getModule('View')->getUrl();
            if (count($details) == 2) {
                if ($details[1] == 'add-category') {
                    Modules::getModule('View')->addJS(Config::WEB_DIR .
                            $this->getDir('/locallabels.js', true));
                }
            } elseif (count($details) == 3) {
                if ($details[1] == 'edit-category') {
                    Modules::getModule('View')->addJS(Config::WEB_DIR .
                            $this->getDir('/locallabels.js', true));
                }
            }
        }
    }

    /**
     * Displays administration interface for adding categories.
     *
     */
    private function displayAdminAddCategory() {
        if (!Modules::getModule('Users')->checkRights('Categories',
                'create categories')) {
            return;
        }
        $categories = &$this->loadCategorySystemTree();
        $params = array('formStart' => $this->getForm('addCategory', true,
                'Categories'), 'addCategory' => $this->lang('addNewCategory'),
                'name' => $this->lang('categoryName'), 'parent' =>
                $this->lang('categoryParent'), 'categories' => $categories,
                'localized' => $this->lang('localized'), 'addLabel' =>
                $this->lang('addLabel'), 'create' => $this->lang('create'),
                'langCode' => $this->lang('langCode'), 'label' =>
                $this->lang('label'), 'description' =>
                $this->lang('description'));
        $this->template('addcategory', $params);
    }

    /**
     * Loads categories without localizations like a tree and returns as list.
     *
     * @param int $root Root category.
     * @param int $level Sub level in category hierarchy.
     * @return Array List of organized categories.
     */
    private function &loadCategorySystemTree($root = 0, $level = 0,
            $ignore = -1) {
        $params = array('order' => array('id'), 'filter' => '`parent` = ' .
                $root);
        $categories = &$this->recordList->getList($this->categoryRecord,
                $params);
        $result = array();
        foreach ($categories as &$category) {
            if ($category['id'] == $ignore) {
                continue;
            }
            $sublevel = &$this->loadCategorySystemTree($category['id'],
                    $level + 1, $ignore);
            $category['name'] = str_repeat('--', $level) . $category['name'];
            $result[] = $category;
            $result = array_merge($result, $sublevel);
        }
        return $result;
    }

    /**
     * Displays administration submenu.
     *
     */
    private function displayAdminSubmenu() {
        $params = array('menu' => array(array('href' => $this->getLink(),
                'title' => $this->lang('listTitle'), 'label' =>
                $this->lang('list')), array('href' =>
                $this->getLink('add-category/'), 'title' =>
                $this->lang('addCategoryTitle'), 'label' =>
                $this->lang('addCategory'))));
        $this->template('submenu', $params);
    }
}
?>
